var append, checkValidComponent, checkValidOp, invertComponent, strInject, text, transformComponent, transformPosition;

text = {};

text.name = 'text';

text.create = text.create = function() {
  return '';
};

strInject = function(s1, pos, s2) {
  return s1.slice(0, pos) + s2 + s1.slice(pos);
};

checkValidComponent = function(c) {
  var d_type, i_type;
  if (typeof c.p !== 'number') throw new Error('component missing position field');
  i_type = typeof c.i;
  d_type = typeof c.d;
  if (!((i_type === 'string') ^ (d_type === 'string'))) {
    throw new Error('component needs an i or d field');
  }
  if (!(c.p >= 0)) throw new Error('position cannot be negative');
};

checkValidOp = function(op) {
  var c, _i, _len;
  for (_i = 0, _len = op.length; _i < _len; _i++) {
    c = op[_i];
    checkValidComponent(c);
  }
  return true;
};

text.apply = function(snapshot, op) {
  var component, deleted, _i, _len;
  checkValidOp(op);
  for (_i = 0, _len = op.length; _i < _len; _i++) {
    component = op[_i];
    if (component.i != null) {
      snapshot = strInject(snapshot, component.p, component.i);
    } else {
      deleted = snapshot.slice(component.p, (component.p + component.d.length));
      if (component.d !== deleted) {
        throw new Error("Delete component '" + component.d + "' does not match deleted text '" + deleted + "'");
      }
      snapshot = snapshot.slice(0, component.p) + snapshot.slice(component.p + component.d.length);
    }
  }
  return snapshot;
};

text._append = append = function(newOp, c) {
  var last, _ref, _ref2;
  if (c.i === '' || c.d === '') return;
  if (newOp.length === 0) {
    return newOp.push(c);
  } else {
    last = newOp[newOp.length - 1];
    if ((last.i != null) && (c.i != null) && (last.p <= (_ref = c.p) && _ref <= (last.p + last.i.length))) {
      return newOp[newOp.length - 1] = {
        i: strInject(last.i, c.p - last.p, c.i),
        p: last.p
      };
    } else if ((last.d != null) && (c.d != null) && (c.p <= (_ref2 = last.p) && _ref2 <= (c.p + c.d.length))) {
      return newOp[newOp.length - 1] = {
        d: strInject(c.d, last.p - c.p, last.d),
        p: c.p
      };
    } else {
      return newOp.push(c);
    }
  }
};

text.compose = function(op1, op2) {
  var c, newOp, _i, _len;
  checkValidOp(op1);
  checkValidOp(op2);
  newOp = op1.slice();
  for (_i = 0, _len = op2.length; _i < _len; _i++) {
    c = op2[_i];
    append(newOp, c);
  }
  return newOp;
};

text.compress = function(op) {
  return text.compose([], op);
};

text.normalize = function(op) {
  var c, newOp, _i, _len;
  newOp = [];
  if ((op.i != null) || (op.p != null)) op = [op];
  for (_i = 0, _len = op.length; _i < _len; _i++) {
    c = op[_i];
    if (c.p == null) c.p = 0;
    append(newOp, c);
  }
  return newOp;
};

transformPosition = function(pos, c, insertAfter) {
  if (c.i != null) {
    if (c.p < pos || (c.p === pos && insertAfter)) {
      return pos + c.i.length;
    } else {
      return pos;
    }
  } else {
    if (pos <= c.p) {
      return pos;
    } else if (pos <= c.p + c.d.length) {
      return c.p;
    } else {
      return pos - c.d.length;
    }
  }
};

text.transformCursor = function(position, op, insertAfter) {
  var c, _i, _len;
  for (_i = 0, _len = op.length; _i < _len; _i++) {
    c = op[_i];
    position = transformPosition(position, c, insertAfter);
  }
  return position;
};

text._tc = transformComponent = function(dest, c, otherC, type) {
  var cIntersect, intersectEnd, intersectStart, newC, otherIntersect, s;
  checkValidOp([c]);
  checkValidOp([otherC]);
  if (c.i != null) {
    append(dest, {
      i: c.i,
      p: transformPosition(c.p, otherC, type === 'right')
    });
  } else {
    if (otherC.i != null) {
      s = c.d;
      if (c.p < otherC.p) {
        append(dest, {
          d: s.slice(0, (otherC.p - c.p)),
          p: c.p
        });
        s = s.slice(otherC.p - c.p);
      }
      if (s !== '') {
        append(dest, {
          d: s,
          p: c.p + otherC.i.length
        });
      }
    } else {
      if (c.p >= otherC.p + otherC.d.length) {
        append(dest, {
          d: c.d,
          p: c.p - otherC.d.length
        });
      } else if (c.p + c.d.length <= otherC.p) {
        append(dest, c);
      } else {
        newC = {
          d: '',
          p: c.p
        };
        if (c.p < otherC.p) newC.d = c.d.slice(0, (otherC.p - c.p));
        if (c.p + c.d.length > otherC.p + otherC.d.length) {
          newC.d += c.d.slice(otherC.p + otherC.d.length - c.p);
        }
        intersectStart = Math.max(c.p, otherC.p);
        intersectEnd = Math.min(c.p + c.d.length, otherC.p + otherC.d.length);
        cIntersect = c.d.slice(intersectStart - c.p, (intersectEnd - c.p));
        otherIntersect = otherC.d.slice(intersectStart - otherC.p, (intersectEnd - otherC.p));
        if (cIntersect !== otherIntersect) {
          throw new Error('Delete ops delete different text in the same region of the document');
        }
        if (newC.d !== '') {
          newC.p = transformPosition(newC.p, otherC);
          append(dest, newC);
        }
      }
    }
  }
  return dest;
};

invertComponent = function(c) {
  if (c.i != null) {
    return {
      d: c.i,
      p: c.p
    };
  } else {
    return {
      i: c.d,
      p: c.p
    };
  }
};

text.invert = function(op) {
  var c, _i, _len, _ref, _results;
  _ref = op.slice().reverse();
  _results = [];
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    c = _ref[_i];
    _results.push(invertComponent(c));
  }
  return _results;
};

if (typeof WEB !== "undefined" && WEB !== null) {
  exports.types || (exports.types = {});
  bootstrapTransform(text, transformComponent, checkValidOp, append);
  exports.types.text = text;
} else {
  module.exports = text;
  require('./helpers').bootstrapTransform(text, transformComponent, checkValidOp, append);
}
